WR.util.namespace('WR.ui');

/**
 * WebRiders PhotoWall
 * -------------------
 * Implements simple photo-wall
 *
 * License: MIT or GPL
 *
 * @author Brizgunov Rostislav (aka kottenator)
 * @versoin 0.5
 *
 * @requires WebRiders Core Framework 0.5+ (webriders.js)
 * @requires jQuery Core 1.4+
 * @requires jQuery overScroll 1.2.1+ (not obligatory, actually. But nice to have)
 *
 * @constructor
 * @param {Object} config - Photo Wall Configuration
 *
 * @config {String/HTMLElement/jQuery} el - Container element. Required!
 * @config {Boolean} editable - Is photo-wall edit mode enabled? Default: false
 * @config {Boolean} undo - Is photo-wall undo mechanism enabled? Default: false (TODO: Under contruction)
 * @config {String} currentLanguage - current language code. Required if you want i18n editor for all languages
 * @config {Object} urls - URLs for all photo-wall actions:
 *     + Update photos     ("update": "URL")
 *     + Update categories (previous update URL used for both photos and categories)
 *     + ...
 *
 *     Example:
 *         1. Set "update" URL: { update: 'http://mysite.com/photowall/update' }
 *         2. Update photos and categories on the wall: changing X, Y, Z-Index or Title
 *         3. Save changes:
 *             AJAX to http://mysite.com/photowall/update
 *             POST JSON data:
 *             {
 *                 photos: [
 *                     {id: 125, x: 345, y: 543},
 *                     {id: 65, z: 23},
 *                     {id:87, title: "qwe qwe", z: 12}
 *                 ],
 *                 categories: [
 *                     {id: 53, x: 36, y: 318, z: 21, title: "zxc zxc zxc"},
 *                     {id: 94, title: "ert ert"}
 *                 ]
 *             }
 *         4. Back-end for the 'http://mysite.com/photowall/update' URL should handle input JSON data with changes
 *            and return operation status
 *
 */
WR.ui.PhotoWall = function(config) {
    if (typeof config == "string" || config instanceof jQuery)
        this.container = $(config);
    else
        $.extend(true, this, config);

    // Post-process container: we need it to be a jQuery
    this.container = $(this.container);

    // Init URLs hash-map
    this.urls = $.extend({}, this.urls);

    // Post-process undo: if undo is enabled, reset it
    if (this.undo)
        this.undo = [];

    // Set few default initial values
    $.extend(this, {
        categories: new WR.data.Collection(), // Override/create category collection
        photos: new WR.data.Collection(), // Override/create photo collection

        changed: false, // We shouldn't have any changes at the beginning!
        saving: false, // We shouldn't save anything at the beginning!
        zoom: 1, // Initial zoom should be 1 (i.e. 100%)

        toolbar: new WR.ui.PhotoWall.Toolbar({ wall: this }), // Override/create toolbar
        itemEditor: new WR.ui.PhotoWall.ItemEditor({
            wall: this,
            currentLanguage: this.currentLanguage,
            languages: this.currentLanguage ? undefined : false
        })
    });
};

WR.ui.PhotoWall.cssPath = ($('link[href*=/photowall/theme-one.css]').attr('href') || '').replace(/theme-one\.css(\?.*)?$/, '');

WR.ui.PhotoWall.prototype = {
    // Configuration params (read-only)
    template: '<div class="wr-photo-wall"></div>',
    container: null,
    urls: {
        update: "./update/"
    },
	cssPath: WR.ui.PhotoWall.cssPath,
    cursors: {
        grab: WR.ui.PhotoWall.cssPath + 'images/grab.cur',
        grabbing: WR.ui.PhotoWall.cssPath + 'images/grabbing.cur'
    },
    editable: false,
    undo: false,
    currentLanguage: null,

    // Private params
    el: null,
    categories: null,
    photos: null,
    toolbar: null,
    itemEditor: null,
    rendered: false,
    zIndexMaximum: 0,
    changed: false,
    saving: false,

    zoom: 1,

    /**
     * Set wall mode - edit or view
     * @param {Boolean} editable - true for edit-mode and false for view. Required!
     * @param {Boolean} keepToolbarVisible - keep toolbar element visible or not. Default: false
     */
    setEditable: function(editable, keepToolbarVisible) {
        this.editable = !!editable;
        keepToolbarVisible = !!keepToolbarVisible;

        if (this.rendered) {
            if (this.editable) {
                this.el.addClass('wr-photo-wall-edit').css('overflow', 'auto')
                    .find('.wr-photo-item, .wr-photo-category').draggable("enable");

                this.toolbar.el.show();

                if ($.fn.overscroll)
                    this.el.overscroll("disable");
            } else {
                this.el.removeClass('wr-photo-wall-edit').css('overflow', 'hidden')
                    .find('.wr-photo-item, .wr-photo-category').draggable("disable");

                if (!keepToolbarVisible)
                    this.toolbar.el.hide();

                this.itemEditor.close().toolbar.detach();

                 if ($.fn.overscroll)
                    this.el.overscroll("enable");
            }
        }
    },

    /**
     * Add item (photo or category) to the wall
     *
     * Two usages:
     *     1. wall.addItem('category', item(s))
     *     2. wall.addItem(item[s]) - resolve type by itself. Default type: 'photo'
     *
     * @param {String} type - item type: 'photo' or 'category'. Required! (but only in Usage #1)
     * @param {WR.ui.PhotoWall.Photo/WR.ui.PhotoWall.Category/Array/Object}
     */
    addItem: function(type, item) {
        var self = this;

        if (arguments.length == 1) {
            item = type;
            type = null;
        }

        if (typeof item == "undefined" || item == null)
            return;

        if ($.isArray(item)) {
            for (var i = 0, l = item.length; i < l; i++)
				this.addItem(type, item[i]);
			return;
        }

        // Resolving type
        if (!type && item instanceof WR.ui.PhotoWall.Category)
            type = 'category';
        // otherwise type is considered as 'photo'

        if (type == 'category') {
            // adding category item(s)
            if (!(item instanceof WR.ui.PhotoWall.Category))
                item = new WR.ui.PhotoWall.Category(item);
            this.categories.push(item);
        } else {
            // adding photo item(s)
            if (!(item instanceof WR.ui.PhotoWall.Photo))
                item = new WR.ui.PhotoWall.Photo(item);
            this.photos.push(item);
        }

        if (item.z > this.zIndexMaximum)
            this.zIndexMaximum = item.z;
        item.wall = this;

        // Bad parody on event dispatching :)
        item.afterRender = this.afterItemRender;

        if (this.rendered)
            item.render();

        return item;
    },

    /**
     * Add photo category to the wall
     * @param {WR.ui.PhotoWall.Category/Array/Object} category - category instance/array/config
     */
    addCategory: function(category) {
        return this.addItem('category', category);
    },

    /**
     * Add photo to the wall
     * @param {WR.ui.PhotoWall.Photo/Array/Object} photo - photo instance/array/config
     */
    addPhoto: function(photo) {
        return this.addItem('photo', photo);
    },

    /**
     * Render photo-wall
     * Need this.container to be already set
     */
    render: function() {
        if (!this.rendered && this.container) {
            var self = this;
			
			// Prepare container
            this.container.css({
                position: 'relative',
				overflow: 'hidden'
            });

            // Create new element
            this.el = $(this.template).prependTo(this.container).css({
                width: this.container.attr('clientWidth'),
                height: this.container.attr('clientHeight')
            });
			
			// Handle window resizing (it may take effect on photowall container size)
			$(window).resize(function() {
				self.el.css({
					width: self.container.attr('clientWidth'),
					height: self.container.attr('clientHeight')
				});
			});

            // render photos, categories and prepare them for collecting changes
            this.categories.each("render");
            this.photos.each("render");

            // Render toolbar
            this.toolbar.render();

            // Render item editor
            this.itemEditor.render();

            // Applying cursor styles
            if (!this.grabCursorsInited) {
                $('head').eq(0).append(
                    '<style type="text/css">' +
                        '/* WebRiders Photo-Wall Styles */' +
                        '.wr-photo-wall-edit .ui-draggable { cursor:url("' + this.cursors.grab + '"), move; }' +
                        '.wr-photo-wall-edit .ui-draggable-dragging { cursor:url("' + this.cursors.grabbing + '"), move; }' +
                    '</style>'
                );
                this.grabCursorsInited = true;
            }

            if ($.fn.overscroll) {
                $.fn.overscroll.icons = {
                    open: this.cursors.grab,
                    closed: this.cursors.grabbing
                };

                // Overscrolling slower in IE
                if ($.browser.msie) {
                    $.extend($.fn.overscroll.constants, {
                        scrollDeltaMod: 1,
                        scrollDuration: 100
                    });
                } else {
                    $.extend($.fn.overscroll.constants, {
                        scrollDeltaMod: 3,
                        scrollDuration: 500
                    });
                }

                this.el.overscroll();
            } else {
                // TODO: make draggable without $.overscroll plug-in
            }

            this.rendered = true;

            // Post-process editable layout
            this.setEditable(this.editable);
        }
    },

    /**
     * Bad parody on event dispatching :)
     * This method will be called just after each wall's item .render()
     * This method will be decorated (enhanced) in the ItemEditor class
     *
     * this {WR.ui.PhotoWall.Photo/WR.ui.PhotoWall.Category}
     */
    afterItemRender: function() {
        this.el.draggable({
            //scrollSensitivity: 200,
            scroll: true
        });
        
		if (!this.wall.editable)
            this.el.draggable("disable");

        this.wall.handleItemDrag(this);
    },

    /**
     * Attach Drag&Drop item listeners to save item changes:
     *     + dragging photo/category (change its x and y)
     *     + changing its title
     *     + changing its Z-Index
     *
     * @param {WR.ui.PhotoWall.Photo/WR.ui.PhotoWall.Category} item
     */
    handleItemDrag: function(item) {
        var onDragStart = function(e, ui) {
            if (!this.editable)
                return;

            ui.helper.css('z-index', ++this.zIndexMaximum);
            var params = { z: this.zIndexMaximum };
            item.change(params);
        };

        var onDragStop = function(e, ui) {
            if (!this.editable)
                return;

            var params = { x: parseInt(ui.position.left) / item.zoom, y: parseInt(ui.position.top) / item.zoom };
            item.change(params);
        };

        item.el.bind("dragstart", $.proxy(onDragStart, this));
        item.el.bind("dragstop", $.proxy(onDragStop, this));
    },

    /**
     * Save all current changes on wall
     *
     * AJAX POST data format:
     *     {
     *         photos: [
     *             {id: 125, x: 345, y: 543, z: 23, title: "qwe qwe"}
     *         ],
     *         categories: [
     *             {id: 53, x: 36, y: 318, z: 21, title: "zxc zxc zxc"}
     *         ]
     *     }
     */
    saveChanges: function() {
        if (this.saving || !this.changed)
            return;

        this.saving = true;

        var changedCategories = this.categories.get({ changes: '*' });
        var changedPhotos = this.photos.get({ changes: '*' });
        var changedData = {
            categories: [],
            photos: []
        };

        this.changed = false;
        $.each(changedCategories, function() {
            changedData.categories.push($.extend({ id: this.id }, this.changes));
            delete this.changes;
        });
        $.each(changedPhotos, function() {
            changedData.photos.push($.extend({ id: this.id }, this.changes));
            delete this.changes;
        });

        $.ajax({
            url: this.urls.update,
            type: 'post',
            data: changedData,
            context: this,

            success: function() {
                this.saving = false;
            },

            error: function() {
                this.saving = false;
                this.changed = true;
                var self = this;
                function restoreChanges(type) { // type: 'categories' or 'photos'
                    return function(i, o) {
                        var id = o.id, item = self[type].getById(id);
                        delete o.id;
                        item.changes = $.extend(o, item.changes);
                    }
                }
                $.each(changedData.categories, restoreChanges('categories'));
                $.each(changedData.photos, restoreChanges('photos'));
            }
        });
    },

    /**
     * Set wall zoom.
     * Normal zoom is 1
     *
     * @param {Number} newZoom - float zoom value
     */
    setZoom: function(newZoom) {
        if (isNaN(newZoom))
            return;

        if (this.rendered) {
            // Apply scroll
            this.el[0].scrollLeft = this.el[0].scrollLeft / this.zoom * newZoom;
            this.el[0].scrollTop = this.el[0].scrollTop / this.zoom * newZoom;
        }

        // Zoom each photo and category item
        this.photos.each(function() { this.setZoom(newZoom); });
        this.categories.each(function() { this.setZoom(newZoom); });

        this.zoom = newZoom * 1;

        // Handle jquery.lazyload
        if ($.fn.lazyload)
            this.el.trigger('scroll');
    }
};
